package com.snack.util;

import java.math.BigDecimal;

/**
 * 精确小数计算<br>
 * 由于Java的基本数据类型不能够对浮点数进行精确运算，<br>
 * 这个工具类提供精确的浮点数运算，包括加减乘除和四舍五入。
 * <p>
 * Copyright: Copyright (c) May 11, 2009
 * <p>
 * Company: 北京宽连十方数字技术有限公司
 * <p>
 * Author: lingf
 * <p>
 * Version: 1.0
 */
public class DoubleUtil {

	/** 默认除法运算精度，10 */
	private static final int DEF_DIV_SCALE = 10;

	/** 截取小数时采用四舍五入操作 */
	public static final int ROUND_MODULE_HALF_UP = 1;

	/** 截取小数时直接将被截取部分进1 */
	public static final int ROUND_MODULE_UP = 2;

	/** 截取小数时直接将被截取部分舍弃 */
	public static final int ROUND_MODULE_DOWN = 3;

	/** 所有方法均用静态方法实现，不允许实例化 */
	private DoubleUtil() {
	}

	/**
	 * 实现浮点数的加法运算功能
	 * 
	 * @param v1
	 *            加数1
	 * @param v2
	 *            加数2
	 * @return v1+v2的和
	 */
	public static double add(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.add(b2).doubleValue();
	}

	/**
	 * 实现浮点数的加法运算功能
	 * 
	 * @param v1
	 *            加数1
	 * @param v2
	 *            加数2
	 * @param scale
	 *            保留小数个数
	 * @param module
	 *            模式：1四舍五入；2直接进1；3直接舍弃
	 * @return
	 */
	public static double add(double v1, double v2, int scale, int module) {
		return format(add(v1, v2), scale, module);
	}

	/**
	 * 实现浮点数的减法运算功能
	 * 
	 * @param v1
	 *            被减数
	 * @param v2
	 *            减数
	 * @return v1-v2的差
	 */
	public static double sub(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.subtract(b2).doubleValue();
	}

	/**
	 * /** 实现浮点数的减法运算功能
	 * 
	 * @param v1
	 *            被减数
	 * @param v2
	 *            减数
	 * @param scale
	 *            保留小数个数
	 * @param module
	 *            模式：1四舍五入；2直接进1；3直接舍弃
	 * @return
	 */
	public static double sub(double v1, double v2, int scale, int module) {
		return format(sub(v1, v2), scale, module);
	}

	/**
	 * 实现浮点数的乘法运算功能
	 * 
	 * @param v1
	 *            被乘数
	 * @param v2
	 *            乘数
	 * @return v1*v2的积
	 */
	public static double multi(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.multiply(b2).doubleValue();
	}

	/**
	 * 实现浮点数的乘法运算功能
	 * @param v1 被乘数
	 * @param v2 乘数
	 * @param scale 保留小数个数
	 * @param module 模式：1四舍五入；2直接进1；3直接舍弃
	 * @return
	 */
	public static double multi(double v1, double v2, int scale, int module) {
		return format(multi(v1, v2), scale, module);
	}

	/**
	 * 实现浮点数的除法运算功能 当发生除不尽的情况时，精确到小数点以后DEF_DIV_SCALE位(默认为10位)，后面的位数进行四舍五入。
	 * 
	 * @param v1
	 *            被除数
	 * @param v2
	 *            除数
	 * @return v1/v2的商
	 */
	public static double div(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

	/**
	 * 实现浮点数的除法运算功能 当发生除不尽的情况时，精确到小数点以后DEF_DIV_SCALE位(默认为10位)，后面的位数进行四舍五入。
	 * @param v1 被除数
	 * @param v2  除数
	 * @param scale 保留小数位数
	 * @param module 模式：1四舍五入；2直接进1；3直接舍弃
	 * @return
	 */
	public static double div(double v1, double v2, int scale, int module) {
		return format(div(v1, v2), scale, module);
	}

	/**
	 * 提供精确的小数位四舍五入功能 <br>
	 * 如 round(1.245, 2) == 1.25
	 * 
	 * @param v
	 *            需要四舍五入的数字
	 * @param scale
	 *            小数点后保留几位
	 * @return 四舍五入后的结果
	 */
	public static double round(double v, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

	/**
	 * 直接根据scale值截取小数位数，截取之后的值始终小于原始值 <br>
	 * 如 down(1.2458, 3) == 1.245
	 * 
	 * @param v
	 *            需要处理的值
	 * @param scale
	 *            小数点后保留位数
	 * @return 截取后的结果
	 */
	public static double down(double v, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_DOWN).doubleValue();
	}

	/**
	 * 直接根据scale值截取小数位数，截取之后的值始终大于原始值 <br>
	 * 如 down(1.2451, 3) == 1.246
	 * 
	 * @param v
	 *            需要处理的值
	 * @param scale
	 *            小数点后保留位数
	 * @return 截取后的结果
	 */
	public static double up(double v, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_UP).doubleValue();
	}

	/**
	 * 格式化数字
	 * 
	 * @param val
	 *            需要格式化的数字
	 * @param scale
	 *            保留小数位数
	 * @param module
	 *            格式化时使用的模式： 1四舍五入；2直接进1；3直接舍弃
	 * @return
	 */
	public static double format(double val, int scale, int module) {
		double rtnVal = val;
		if (module == ROUND_MODULE_HALF_UP) { // 需要四舍五入
			rtnVal = round(val, scale);
		} else if (module == ROUND_MODULE_DOWN) { // 直接截取掉
			rtnVal = down(val, scale);
		} else if (module == ROUND_MODULE_UP) { // 截取并进1
			rtnVal = up(val, scale);
		}
		return rtnVal;
	}
}